package com.tbynet.jwp.framework.aop;

import com.jfinal.aop.AopManager;
import com.jfinal.kit.StrKit;
import com.jfinal.log.Log;
import com.jfinal.plugin.IPlugin;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;

/**
 * AOP 自动映射插件
 * 自动扫描指定包下的类，并根据 @Aop 注解自动注册 AOP 映射
 * 支持从文件系统和 JAR 包中扫描类文件
 *
 * @author tbynet
 * @since 1.0
 */
public class AopMappingPlugin implements IPlugin {

    private static final Log log = Log.getLog(AopMappingPlugin.class);

    // ============ 常量定义 ============

    /** 类文件后缀 */
    private static final String CLASS_FILE_SUFFIX = ".class";
    /** JAR 协议标识 */
    private static final String JAR_PROTOCOL = "jar";
    /** 文件协议标识 */
    private static final String FILE_PROTOCOL = "file";
    /** UTF-8 编码 */
    private static final String UTF_8 = "UTF-8";
    /** Service 类后缀 */
    private static final String SERVICE_SUFFIX = "Service";

    // ============ 静态状态管理 ============

    /** 已扫描的类名集合，用于避免重复处理 - 使用 ConcurrentHashMap 提高并发性能 */
    private static final Set<String> SCANNED_CLASS_NAMES = ConcurrentHashMap.newKeySet();

    // ============ 实例配置和状态 ============

    /** 基础包路径 */
    private final String basePackage;
    /** 标准化后的基础包路径（将 . 替换为 /） */
    private final String normalizedBasePackage;
    /** 类跳过过滤器 */
    private final Predicate<String> classSkip;
    /** AOP 管理器实例 */
    private final AopManager aopManager;

    /** 扫描类计数器 - 使用 AtomicInteger 保证原子性 */
    private final AtomicInteger scanCount = new AtomicInteger(0);
    /** 映射计数器 - 使用 AtomicInteger 保证原子性 */
    private final AtomicInteger mappingCount = new AtomicInteger(0);
    /** 插件启动状态标志 - 使用 volatile 保证可见性 */
    private volatile boolean started = false;

    /** 类加载器缓存 */
    private volatile ClassLoader classLoader;

    /** 资源过滤器 - 使用 volatile 保证可见性 */
    private static volatile Predicate<URL> resourceFilter = null;

    // ============ 构造函数 ============

    /**
     * 构造函数
     * @param basePackage 要扫描的基础包名
     * @param classSkip 类跳过条件过滤器
     */
    public AopMappingPlugin(String basePackage, Predicate<String> classSkip) {
        validateBasePackage(basePackage);

        this.basePackage = basePackage;
        this.normalizedBasePackage = normalizeBasePackage(basePackage);
        this.classSkip = classSkip;
        this.aopManager = AopManager.me();
    }

    /**
     * 构造函数（无类跳过过滤器）
     * @param basePackage 要扫描的基础包名
     */
    public AopMappingPlugin(String basePackage) {
        this(basePackage, null);
    }

    // ============ 插件生命周期方法 ============

    /**
     * 启动插件
     * 扫描指定包下的所有类，注册带有 @Aop 注解的类
     * @return true-启动成功, false-启动失败
     */
    @Override
    public boolean start() {
        if (started) {
            log.warn("AopMappingPlugin 已经启动，跳过重复启动");
            return true;
        }

        log.info("开始启动 AopMappingPlugin，基础包: %s", basePackage);
        long startTime = System.nanoTime();

        try {
            // 初始化插件
            initialize();
            // 扫描类路径资源
            List<URL> resources = scanClasspathResources();
            // 处理所有找到的资源
            processResources(resources);

            started = true;
            long costTime = (System.nanoTime() - startTime) / 1_000_000;

            log.info("AopMappingPlugin 启动完成 - 扫描类: %s个, 映射: %s个, 耗时: %sms",
                    scanCount.get(), mappingCount.get(), costTime);
            return true;

        } catch (Exception e) {
            log.error("AopMappingPlugin 启动失败，基础包: %s", basePackage, e);
            throw new PluginStartException("AopMappingPlugin 启动失败: " + e.getMessage(), e);
        }
    }

    /**
     * 停止插件
     * 清理所有缓存状态和资源
     * @return true-停止成功, false-停止失败
     */
    @Override
    public boolean stop() {
        if (!started) {
            log.warn("AopMappingPlugin 未启动，无需停止");
            return true;
        }

        try {
            cleanup();
            log.info("AopMappingPlugin 已停止");
            return true;
        } catch (Exception e) {
            log.error("AopMappingPlugin 停止失败", e);
            return false;
        }
    }

    // ============ 公共方法 ============

    /**
     * 获取插件启动状态
     * @return true-已启动, false-未启动
     */
    public boolean isStarted() {
        return started;
    }

    /**
     * 设置资源过滤器
     * 用于过滤不需要扫描的 URL 资源
     * @param filter 资源过滤器
     */
    public static void setResourceFilter(Predicate<URL> filter) {
        resourceFilter = filter;
    }

    /**
     * 获取扫描统计信息
     * @return 扫描统计信息字符串
     */
    public String getScanStats() {
        return String.format("扫描类: %d个, 映射: %d个", scanCount.get(), mappingCount.get());
    }

    /**
     * 获取扫描的类数量
     * @return 扫描的类数量
     */
    public int getScanCount() {
        return scanCount.get();
    }

    /**
     * 获取注册的映射数量
     * @return 注册的映射数量
     */
    public int getMappingCount() {
        return mappingCount.get();
    }

    // ============ 核心扫描逻辑 ============

    /**
     * 初始化插件
     * 设置类加载器等必要资源
     */
    private void initialize() {
        this.classLoader = getClassLoader();
    }

    /**
     * 扫描类路径资源
     * 获取指定包名对应的所有 URL 资源
     * @return 找到的资源 URL 列表
     * @throws IOException 如果扫描过程中发生 IO 异常
     */
    private List<URL> scanClasspathResources() throws IOException {
        Set<String> processedUrls = new LinkedHashSet<>(); // 保持顺序
        List<URL> result = new ArrayList<>();

        Enumeration<URL> urls = classLoader.getResources(normalizedBasePackage);
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();

            if (shouldSkipResource(url)) {
                log.debug("跳过资源: %s", url);
                continue;
            }

            String urlString = url.toString();
            if (processedUrls.add(urlString)) {
                result.add(url);
                log.debug("添加扫描资源: %s", url);
            }
        }

        if (result.isEmpty()) {
            log.warn("在类路径下未找到包: %s 对应的资源", basePackage);
        }

        return result;
    }

    /**
     * 处理所有资源
     * 遍历资源列表并逐个处理
     * @param resources 要处理的资源列表
     * @throws IOException 如果处理过程中发生 IO 异常
     */
    private void processResources(List<URL> resources) throws IOException {
        for (URL resource : resources) {
            try {
                processSingleResource(resource);
            } catch (Exception e) {
                log.warn("处理资源失败: %s", resource, e);
                // 继续处理其他资源，不中断整个扫描过程
            }
        }
    }

    /**
     * 处理单个资源
     * 根据资源协议类型分发到不同的处理方法
     * @param resource 要处理的资源
     * @throws IOException 如果处理过程中发生 IO 异常
     */
    private void processSingleResource(URL resource) throws IOException {
        String protocol = resource.getProtocol();
        switch (protocol) {
            case JAR_PROTOCOL:
                processJarResource(resource);
                break;
            case FILE_PROTOCOL:
                processFileResource(resource);
                break;
            default:
                log.warn("不支持的协议类型: %s, 资源: %s", protocol, resource);
        }
    }

    /**
     * 处理 JAR 资源
     * 打开 JAR 文件并处理其中的类文件条目
     * @param url JAR 资源 URL
     * @throws IOException 如果打开或读取 JAR 文件失败
     */
    private void processJarResource(URL url) throws IOException {
        URLConnection connection = url.openConnection();
        if (connection instanceof JarURLConnection) {
            JarURLConnection jarConnection = (JarURLConnection) connection;
            try (JarFile jarFile = jarConnection.getJarFile()) {
                processJarEntries(jarFile);
            }
        } else {
            log.warn("非 JarURLConnection 类型: %s", connection.getClass().getName());
        }
    }

    /**
     * 处理 JAR 文件条目
     * 遍历 JAR 文件中的所有条目，处理类文件
     * @param jarFile 要处理的 JAR 文件
     */
    private void processJarEntries(JarFile jarFile) {
        Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            String entryName = entry.getName();

            if (isClassFileInBasePackage(entryName)) {
                String className = extractClassNameFromJarEntry(entryName);
                processClass(className);
            }
        }
    }

    /**
     * 处理文件资源
     * 处理文件系统中的目录资源
     * @param url 文件资源 URL
     */
    private void processFileResource(URL url) {
        String filePath = decodeUrl(url.getPath());
        Path baseDir = Paths.get(filePath);

        if (!Files.exists(baseDir)) {
            log.warn("目录不存在: %s", baseDir);
            return;
        }

        if (!Files.isDirectory(baseDir)) {
            log.warn("路径不是目录: %s", baseDir);
            return;
        }

        String classPath = determineClassPath(baseDir.toFile());
        scanDirectoryWithNIO(baseDir, classPath);
    }

    /**
     * 使用 NIO.2 API 扫描目录
     * 使用 Files.walk 流式 API 提高扫描性能
     * @param baseDir 基础目录路径
     * @param classPath 类路径前缀
     */
    private void scanDirectoryWithNIO(Path baseDir, String classPath) {
        try (Stream<Path> paths = Files.walk(baseDir)) {
            paths.filter(Files::isRegularFile)
                    .filter(path -> path.toString().endsWith(CLASS_FILE_SUFFIX))
                    .forEach(path -> {
                        String className = extractClassNameFromPath(path, classPath);
                        processClass(className);
                    });
        } catch (IOException e) {
            log.error("扫描目录失败: %s", baseDir, e);
        }
    }

    /**
     * 使用 FileVisitor 扫描目录（替代方案）
     * 提供另一种目录遍历方式
     * @param baseDir 基础目录路径
     * @param classPath 类路径前缀
     */
    private void scanDirectoryWithFileVisitor(Path baseDir, String classPath) {
        try {
            Files.walkFileTree(baseDir, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                    if (file.toString().endsWith(CLASS_FILE_SUFFIX)) {
                        String className = extractClassNameFromPath(file, classPath);
                        processClass(className);
                    }
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult visitFileFailed(Path file, IOException exc) {
                    log.warn("访问文件失败: %s", file, exc);
                    return FileVisitResult.CONTINUE;
                }
            });
        } catch (IOException e) {
            log.error("遍历目录失败: %s", baseDir, e);
        }
    }

    // ============ 类处理逻辑 ============

    /**
     * 处理单个类
     * 加载类并检查是否需要注册 AOP 映射
     * @param className 完整类名
     */
    private void processClass(String className) {
        if (shouldSkipClass(className)) {
            return;
        }

        scanCount.incrementAndGet();

        // 使用类名去重，避免重复处理
        if (!SCANNED_CLASS_NAMES.add(className)) {
            return;
        }

        Class<?> clazz = loadClass(className);
        if (clazz != null) {
            registerAopMapping(clazz);
        }
    }

    /**
     * 注册 AOP 映射
     * 检查类的 @Aop 注解并注册到 AOP 管理器
     * @param implementationClass 实现类
     */
    @SuppressWarnings("unchecked")
    private void registerAopMapping(Class<?> implementationClass) {
        if (!isValidImplementationClass(implementationClass)) {
            return;
        }

        Aop aopAnnotation = implementationClass.getAnnotation(Aop.class);
        if (aopAnnotation == null) {
            return;
        }

        try {
            Class<?> targetClass = determineTargetClass(aopAnnotation, implementationClass);
            if (isValidTargetMapping(targetClass, implementationClass)) {
                mappingCount.incrementAndGet();
                if (log.isDebugEnabled()) {
                    log.debug("注册 AOP 映射: %s -> %s", targetClass.getName(),
                            implementationClass.getName());
                }
                aopManager.addMapping((Class<Object>) targetClass, implementationClass);
            } else {
                log.warn("无效的 AOP 映射，目标类: %s, 实现类: %s",
                        targetClass != null ? targetClass.getName() : "null",
                        implementationClass.getName());
            }
        } catch (Exception e) {
            log.error("注册 AOP 映射失败，实现类: %s", implementationClass.getName(), e);
        }
    }

    // ============ 工具方法 ============

    /**
     * 获取类加载器
     * 优先使用线程上下文类加载器，失败时使用当前类的类加载器
     * @return 类加载器实例
     */
    private ClassLoader getClassLoader() {
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        return contextClassLoader != null ? contextClassLoader : getClass().getClassLoader();
    }

    /**
     * 验证基础包名
     * @param basePackage 要验证的基础包名
     * @throws IllegalArgumentException 如果包名为空或空白
     */
    private void validateBasePackage(String basePackage) {
        if (StrKit.isBlank(basePackage)) {
            throw new IllegalArgumentException("基础包名不能为空");
        }
    }

    /**
     * 标准化基础包名
     * 将包名中的 . 替换为 /，并确保以 / 结尾
     * @param basePackage 原始基础包名
     * @return 标准化后的包名
     */
    private String normalizeBasePackage(String basePackage) {
        return basePackage.replace('.', '/')
                .replaceAll("/+$", "") + '/'; // 确保以 / 结尾且没有重复
    }

    /**
     * 判断是否应该跳过资源
     * @param url 要检查的资源 URL
     * @return true-应该跳过, false-不应该跳过
     */
    private boolean shouldSkipResource(URL url) {
        return resourceFilter != null && !resourceFilter.test(url);
    }

    /**
     * 判断是否应该跳过类
     * @param className 要检查的类名
     * @return true-应该跳过, false-不应该跳过
     */
    private boolean shouldSkipClass(String className) {
        return classSkip != null && classSkip.test(className);
    }

    /**
     * 判断是否为类文件且在基础包内
     * @param entryName JAR 条目名称
     * @return true-是目标类文件, false-不是目标类文件
     */
    private boolean isClassFileInBasePackage(String entryName) {
        return entryName.endsWith(CLASS_FILE_SUFFIX) && entryName.startsWith(normalizedBasePackage);
    }

    /**
     * 从 JAR 条目中提取类名
     * 将路径分隔符替换为 .，并去除 .class 后缀
     * @param entryName JAR 条目名称
     * @return 完整类名
     */
    private String extractClassNameFromJarEntry(String entryName) {
        return entryName.substring(0, entryName.length() - CLASS_FILE_SUFFIX.length())
                .replace('/', '.');
    }

    /**
     * 从文件路径中提取类名
     * 将文件路径转换为类名格式
     * @param path 文件路径
     * @param classPath 类路径前缀
     * @return 完整类名
     */
    private String extractClassNameFromPath(Path path, String classPath) {
        String absolutePath = path.toAbsolutePath().toString();
        String relativePath = absolutePath.substring(classPath.length(),
                absolutePath.length() - CLASS_FILE_SUFFIX.length());
        return relativePath.replace(File.separatorChar, '.');
    }

    /**
     * 确定类路径
     * 从目录路径中提取类路径前缀
     * @param baseDir 基础目录
     * @return 类路径前缀
     */
    private String determineClassPath(File baseDir) {
        String absolutePath = baseDir.getAbsolutePath();
        if (!absolutePath.endsWith(File.separator)) {
            absolutePath += File.separator;
        }

        String packagePath = normalizedBasePackage.replace('/', File.separatorChar);
        int packageIndex = absolutePath.lastIndexOf(packagePath);

        return packageIndex != -1 ? absolutePath.substring(0, packageIndex) : absolutePath;
    }

    /**
     * 验证实现类是否有效
     * 检查类是否为公共的、非抽象、非接口的具体类
     * @param clazz 要检查的类
     * @return true-有效实现类, false-无效实现类
     */
    private boolean isValidImplementationClass(Class<?> clazz) {
        int modifiers = clazz.getModifiers();
        return Modifier.isPublic(modifiers) &&
                !Modifier.isAbstract(modifiers) &&
                !Modifier.isInterface(modifiers);
    }

    /**
     * 确定目标类
     * 根据 @Aop 注解配置确定目标类
     * @param aopAnnotation AOP 注解实例
     * @param implementationClass 实现类
     * @return 目标类
     */
    private Class<?> determineTargetClass(Aop aopAnnotation, Class<?> implementationClass) {
        Class<?> explicitTarget = aopAnnotation.value();
        if (explicitTarget != Void.class) {
            return explicitTarget;
        }
        return findDefaultTargetClass(implementationClass);
    }

    /**
     * 查找默认目标类
     * 按照接口优先、父类次之、自身最后的顺序查找目标类
     * @param implementationClass 实现类
     * @return 默认目标类
     */
    private Class<?> findDefaultTargetClass(Class<?> implementationClass) {
        // 优先查找 Service 接口
        Class<?>[] interfaces = implementationClass.getInterfaces();
        Class<?> serviceInterface = findServiceInterface(interfaces);
        if (serviceInterface != null) {
            return serviceInterface;
        }

        // 查找父类
        Class<?> superClass = implementationClass.getSuperclass();
        if (superClass != null && superClass != Object.class) {
            return superClass;
        }

        // 返回实现类本身
        return implementationClass;
    }

    /**
     * 查找 Service 接口
     * 优先查找名称以 Service 结尾的接口
     * @param interfaces 接口数组
     * @return Service 接口，如果找不到则返回 null
     */
    private Class<?> findServiceInterface(Class<?>[] interfaces) {
        if (interfaces.length == 0) {
            return null;
        }

        // 优先查找名称匹配的接口
        for (Class<?> iface : interfaces) {
            if (iface.getSimpleName().endsWith(SERVICE_SUFFIX)) {
                return iface;
            }
        }

        // 返回唯一的接口
        return interfaces.length == 1 ? interfaces[0] : null;
    }

    /**
     * 验证目标映射是否有效
     * 检查目标类和实现类之间的映射关系是否有效
     * @param targetClass 目标类
     * @param implementationClass 实现类
     * @return true-有效映射, false-无效映射
     */
    private boolean isValidTargetMapping(Class<?> targetClass, Class<?> implementationClass) {
        if (targetClass == null || targetClass == Void.class) {
            return false;
        }

        if (targetClass.isInterface()) {
            return targetClass.isAssignableFrom(implementationClass);
        } else {
            // 对于类，确保不是自映射且存在继承关系
            return !targetClass.equals(implementationClass) &&
                    targetClass.isAssignableFrom(implementationClass);
        }
    }

    /**
     * 加载类
     * 使用插件类加载器加载指定类
     * @param className 要加载的类名
     * @return 加载的类，如果加载失败则返回 null
     */
    private Class<?> loadClass(String className) {
        try {
            return Class.forName(className, false, classLoader);
        } catch (Throwable t) {
            log.debug("无法加载类: %s", className);
            return null;
        }
    }

    /**
     * URL 解码
     * 对 URL 路径进行 UTF-8 解码
     * @param url 要解码的 URL 字符串
     * @return 解码后的字符串
     */
    private String decodeUrl(String url) {
        try {
            return URLDecoder.decode(url, UTF_8);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("URL 解码失败", e);
        }
    }

    /**
     * 清理资源
     * 重置所有状态和缓存
     */
    private void cleanup() {
        SCANNED_CLASS_NAMES.clear();
        classLoader = null;
        started = false;
        scanCount.set(0);
        mappingCount.set(0);
    }

    // ============ 自定义异常 ============

    /**
     * 插件启动异常
     * 用于包装插件启动过程中的异常
     */
    public static class PluginStartException extends RuntimeException {
        public PluginStartException(String message) {
            super(message);
        }

        public PluginStartException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    // ============ 测试入口 ============

    /**
     * 测试入口方法
     * 演示插件的使用方法
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        AopMappingPlugin plugin = new AopMappingPlugin("com.tbynet.jwp.service.provider");

        // 添加资源过滤器示例
        setResourceFilter(url -> !url.toString().contains("test"));

        // 添加类跳过过滤器示例
        AopMappingPlugin pluginWithFilter = new AopMappingPlugin(
                "com.tbynet.jwp.service",
                className -> className.contains("Test") || className.contains("Mock")
        );

        plugin.start();
    }
}